package edu.cmu.javadb;

import static edu.cmu.javadb.plan.condition.Condition.equal;

import java.util.*;

import edu.cmu.javadb.exceptions.*;
import edu.cmu.javadb.plan.PlanNode;
import edu.cmu.javadb.plan.Table;
import edu.cmu.javadb.plan.condition.*;


public class QueryableSet<T> extends HashSet<T> implements Queryable<T> {
	private static final long serialVersionUID = 452341597809983753L;

	@Override
	public QueryableSet<T> filter(Condition cond) {
		QueryableSet<T> res = new QueryableSet<T>();
		for (T t : this) {
			if (cond.satisfy(t)) {
				res.add(t);
			}
		}
		return res;
	}

	@Override
	public <T2> QueryableSet<T2> project(Path path) throws NotProjectableException {
		QueryableSet<T2> res = new QueryableSet<T2>();

		if (path.getDepth() > 1) {
			throw new NotProjectableException();
		}

		if (this.size() == 0) {
			return res;
		}
		
		try {
			Attribute att = new Attribute(this.iterator().next().getClass(), path.toString());
			for (T t : this) {
				res.add((T2)att.getValue(t));
			}
		} catch (NoAttributeException e) {
			e.printStackTrace();
		}

		return res;
	}

	@Override
	public <T2> QueryableSet<T2> flatten() throws NotFlattenableException {
		QueryableSet<T2> res = new QueryableSet<T2>();

		for (T t : this) {
			if (t instanceof Collection<?>) {
				res.addAll((Collection<T2>) t);
			} else {
				throw new NotFlattenableException();
			}
		}

		return res;
	}
	
	@Override
	public <T2> QueryableSet<T> join(Queryable<T2> c2, Path p1, Path p2)  {
		QueryableSet<T> res = new QueryableSet<T>();
		
		//throws join exception
		
		if (this.size() == 0) {
			return res;
		}
		
		try {
			Attribute a1 = new Attribute(this.iterator().next().getClass(), p1.toString());

			for (T t : this) {
				Object v1 = a1.getValue(t);
				Queryable<T2> tmp = c2.filter(equal(p2, v1));
				if (tmp.execute().size() > 0) {
					res.add(t);
				}
			}
		} catch (NoAttributeException e) {
			e.printStackTrace();
		}
		return res;
	}

	@Override
	public QueryableSet<T> execute() {
		return this;
	}
	
	private PlanNode plan = new Table(this);
	@Override
	public PlanNode getPlan() {
		return plan;
	}

	@Override
	public QueryableSet<T> union(Queryable<T>... args) {
		QueryableSet<T> res = new QueryableSet<T>();
		res.addAll(this);
		
		for (Queryable<T> q : args) {
			res.addAll(q.execute());
		}
		return null;
	}

	@Override
	public <K> VTable<K, T> groupBy(Path path) {
		VTable<K, T> res = new VTable<K, T>();
		Attribute att = null;
		for (T t: this) {
			if (att == null) {
				try {
					att = new Attribute(t.getClass(), path.toString());
				} catch (NoAttributeException e) {
					e.printStackTrace();
				}
			}
			
			K key = att.getValue(t);
			res.addValue(key, t);
		}
		return res;
	}

	@Override
	public <S> S reduce(ReduceFn<S, T> fn) {
		return fn.process(this);
	}
}
